Apple data using SimpleTidy Workflow

Here we use another data set trying to go through the same workflow.

Package requirement

library(svglite)
library(limma)
library(tidyverse)
── Attaching core tidyverse packages ─────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(ggplot2)
library(igraph)

Attaching package: ‘igraph’

The following objects are masked from ‘package:lubridate’:

    %--%, union

The following objects are masked from ‘package:dplyr’:

    as_data_frame, groups, union

The following objects are masked from ‘package:purrr’:

    compose, simplify

The following object is masked from ‘package:tidyr’:

    crossing

The following object is masked from ‘package:tibble’:

    as_data_frame

The following objects are masked from ‘package:stats’:

    decompose, spectrum

The following object is masked from ‘package:base’:

    union
library(ggraph)

library(readxl)
library(patchwork)
library(RColorBrewer)
library(viridis)
Loading required package: viridisLite

Data resource

Malus domestica. The data frame contains microarray gene expression data, with each row corresponding to a gene and each column representing expression levels at different time points. The data includes the mean (Mean), number of data points (n), and standard error (SE). For the 0 DAA time point, only one biological replicate was sampled. For other time points, the data is based on two biological replicates and two technical replicates. In some cases, data points were excluded for technical reasons, so the mean and standard error are only calculated based on the remaining data.

apple_exp = read_excel("Apple_DAA_expression.xls",col_types = "text")
dim(apple_exp)
[1] 15987    26

There are 15987 genes and 8 time point, DAA stands for Days After Anthesis.

# general check on the df info

apple_exp %>%
  mutate(across(ends_with("Mean"), as.numeric)) %>%
  select(ends_with("Mean")) %>%
  summary()
   0 DAA Mean       14 DAA Mean        25 DAA Mean         35 DAA Mean        60 DAA Mean       87 DAA Mean       132 DAA Mean       146 DAA Mean     
 Min.   : 0.0970   Min.   : 0.08612   Min.   :  0.08957   Min.   : 0.08781   Min.   : 0.1013   Min.   : 0.0738   Min.   : 0.08753   Min.   : 0.06487  
 1st Qu.: 0.3528   1st Qu.: 0.36176   1st Qu.:  0.35577   1st Qu.: 0.35565   1st Qu.: 0.3618   1st Qu.: 0.3710   1st Qu.: 0.36312   1st Qu.: 0.38478  
 Median : 0.6993   Median : 0.71192   Median :  0.70186   Median : 0.71305   Median : 0.7226   Median : 0.7375   Median : 0.71482   Median : 0.75483  
 Mean   : 3.3384   Mean   : 3.33391   Mean   :  3.31275   Mean   : 3.44957   Mean   : 3.4013   Mean   : 3.4371   Mean   : 3.32117   Mean   : 3.36191  
 3rd Qu.: 2.0028   3rd Qu.: 2.02268   3rd Qu.:  2.02592   3rd Qu.: 2.05144   3rd Qu.: 2.0625   3rd Qu.: 2.0951   3rd Qu.: 1.99989   3rd Qu.: 2.14520  
 Max.   :73.1040   Max.   :95.82664   Max.   :100.00000   Max.   :73.81931   Max.   :78.6526   Max.   :61.7287   Max.   :77.80648   Max.   :68.08869  
 NA's   :442       NA's   :64         NA's   :115         NA's   :196        NA's   :175       NA's   :319       NA's   :130        NA's   :193       

Usually, when the missing values were caused by very low expression values or not detected, they were generally replaced by 0. However, according to the article, the NA part of this data frame was excluded for technical reasons, so here we employed an exclusion strategy, i.e., we did not select the genes containing the missing values for the subsequent analysis.

apple_exp = apple_exp %>%
  mutate(across(ends_with("Mean"), as.numeric)) %>%
  select(`Genbank number`,ends_with("Mean")) %>% # SE and n is good to know, but not for subsequent analysis
  drop_na()

dim(apple_exp) # 836 genes excluded, 5%. Validation required later.
[1] 15151     9
write.table(apple_exp %>%
  distinct(`Genbank number`), "genbank_apple.csv", row.names = FALSE, col.names = FALSE, quote = FALSE)

PCA


ggplot(apple_exp, aes(x = `0 DAA Mean`)) +
  geom_histogram(bins = 30) 

ggsave("hist_before.svg", height = 3, width = 4, bg ="white")
ggsave("hist_before.png", height = 3, width = 4, bg ="white")

# dev.off()
apple_exp_log = apple_exp %>%
  mutate(across(ends_with("Mean"), ~ log10(. + 1))) # log transform the df

ggplot(apple_exp_log, aes(x = `0 DAA Mean`)) +
  geom_histogram(bins = 30) # The data distribution is still skewed due to the low expression values of most genes, but it is better than using raw values

ggsave("hist_after.svg", height = 3, width = 4, bg ="white")
ggsave("hist_after.png", height = 3, width = 4, bg ="white")

apple_pca = prcomp(t(apple_exp_log[, -1]))
apple_pca_imp = as.data.frame(t(summary(apple_pca)$importance))
apple_pca_coord = apple_pca$x[, 1:8] %>% # Take the all 8 PCs
  as.data.frame() %>% 
  mutate(timepoint = row.names(.)) # rownames to col
apple_pca_coord

According to the paper, DAA and the fruit development stage have a certain corresponding relationship:

0-35 DAA : Cell division 60-87 DAA : Starch accumulation 132-146 DA : Ripening

As we don’t have many combination of the libraries, we won’t expect a strong explanation by stage but it worth a look.

# mutate a stage column
apple_pca_coord = apple_pca_coord %>%
  mutate(stage = case_when(
    timepoint %in% c("0 DAA Mean", "14 DAA Mean", "25 DAA Mean", "35 DAA Mean") ~ "Cell division",
    timepoint %in% c("60 DAA Mean", "87 DAA Mean") ~ "Starch accumulation",
    timepoint %in% c("132 DAA Mean", "146 DAA Mean") ~ "Ripening",
    TRUE ~ "Unknown"
  )) %>%
  mutate(timepoint = str_replace(timepoint, " Mean", "")) # Remove the "Mean" word in timepoint col

apple_pca_coord %>% 
  ggplot(aes(x = PC1, y = PC2)) +
  geom_point(aes(fill = stage), color = "grey20", shape = 21, size = 3, alpha = 0.8) +
   scale_fill_manual(values = c("Cell division" = "#1b9e77", "Starch accumulation" = "#d95f02", "Ripening" = "#7570b3")) +
  labs(x = paste("PC1 (", apple_pca_imp[1, 2] %>% signif(3)*100, "% of Variance)", sep = ""), 
       y = paste("PC2 (", apple_pca_imp[2, 2] %>% signif(3)*100, "% of Variance)", "  ", sep = ""),
       fill = "stage") +  
  theme_bw() +
  theme(
    text = element_text(size= 14),
    axis.text = element_text(color = "black")
  )

ggsave("pca_apple.svg", height = 4, width = 8, bg ="white")
ggsave("pca_apple.png", height = 4, width = 8, bg ="white")

Since PCA is performed based on gene expression data at different time points, this naturally causes different developmental stages to show some separation in PCA space. However, PCA itself is unsupervised and does not know the order of time points, while we see the ordering of PC1 is consistent with the order of time points, and the interval between different stages is obvious. This suggests that the closer the time is to late development, the expression pattern will gradually change, and PCA has captured this feature.

Bait gene

As the rows in the matrix are not actual gene names, and I didn’t map the probes to the genes, it’s hard to get the bait genes. However, the paper provide three core cell cycle genes, which are: EB107042: CDKB1;2 homologue, CN943384: CDKB2;2 homologue, EB141951: CKS1 homologue

We’ll use this three to serve as bait gene

bait_genes = c("EB107042","CN943384","EB141951")

Duplicated genes

apple_exp_log %>%
  dim()
[1] 15151     9
apple_exp_log %>%
  distinct(`Genbank number`) %>%
  dim()
[1] 14902     1

We notice that 15151 > 14902. There must be duplicated combination:

The reason for this “duplication” is that we deleted the ESTs (Expressed Sequence Tags) column at the beginning of the data processing and used the Genbank number as the gene identification.

This may represent different fragments or transcripts of the same gene. We should alter based on the gene identification to differentiate them.

apple_exp_log = apple_exp_log %>%
  group_by(`Genbank number`) %>%
  mutate(gene = case_when( # mutate a new col 
    n() > 1 ~ paste0(`Genbank number`, "_", row_number()), # "duplicates" will have more than 1 row. Add a symbol.
    TRUE ~ `Genbank number` # keep the same for those not_duplicated
  )) %>%
  ungroup() # %>%
  # filter(gene != `Genbank number`) # how to check for the altered rows

Gene selection

# long version
apple_exp_log_long = apple_exp_log %>%
  rename_with(~ gsub(" Mean", "", .), contains("DAA")) %>% # Remove the "Mean" in colnames
  pivot_longer(
    cols = starts_with("0 DAA"):starts_with("146 DAA"), 
    names_to = "timepoint", 
    values_to = "log_exp"
  ) 

As we know, the number of correlations scales to the square of number of genes. In order to calculate gene correlation between each other, 15000+ genes are too much. We can select only the high variance genes, as a gene is unlikely to be involved in a particular stage if it’s expressed at a similar level across all timepoints.

# Calculate the rank
apple_var_rank = apple_exp_log_long %>% 
  group_by(gene) %>% 
  summarise(var = var(log_exp)) %>% # calculate the variance for each gene
  ungroup() %>% 
  mutate(rank = rank(var, ties.method = "average")) # rank the genes

head(apple_var_rank)
# If we take the top 1/3 of the highest variance genes
high_var_apple = apple_exp_log_long %>% 
  group_by(gene) %>% 
  summarise(var = var(log_exp)) %>% 
  ungroup() %>% 
  filter(var > quantile(var, 0.667)) 

# And take the top 3000
high_var_apple3000 = high_var_apple %>% 
  slice_max(order_by = var, n = 3000) 
bait_var = apple_var_rank %>%
  filter(gene %in% bait_genes) 
# Check whether top 3000 can represent the genes of highest variance
apple_var_rank %>% 
  ggplot(aes(x = var, y = rank)) +
   geom_rect( 
    xmax = max(high_var_apple3000$var), 
    xmin = min(high_var_apple3000$var),
    ymax = nrow(apple_var_rank),
    ymin = nrow(apple_var_rank) - 3000,
    fill = "dodgerblue2", alpha = 0.2
    ) +
  geom_hline(
    data = bait_var, aes(yintercept = rank),
    color = "tomato1", size = 0.8, alpha = 0.5
  ) +
  geom_vline(
    data = bait_var, aes(xintercept = var), 
    color = "tomato1", size = 0.8, alpha = 0.5
  ) + 
  geom_line(size = 1.1) +
  labs(y = "rank",
       x = "variance",
       caption = "Blue box = top 3000 high var genes.") +
  theme_classic() +
  theme(
    text = element_text(size = 14),
    axis.text = element_text(color = "black"),
    plot.caption = element_text(hjust = 0)
  )
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
ggsave("highvar3000.svg", height = 4, width = 5, bg ="white")
ggsave("highvar3000.png", height = 4, width = 5, bg ="white")

It looks like if we take the top 3000 genes, it takes pretty much the entire upper elbow of the graph. And all the bait genes are just included.

# Select high var gene
apple_exp_log_highvar = apple_exp_log_long %>% 
  filter(gene %in% high_var_apple3000$gene)
dim(apple_exp_log_highvar)
[1] 24000     4
filtered_genes <- apple_exp_log_highvar %>%
  distinct(`Genbank number`)

write.table(filtered_genes, "high_var_apple.csv", row.names = FALSE, col.names = FALSE, quote = FALSE)

Gene-wise correlation

# pivot wider for as input for correlation
highvar_log_wide = apple_exp_log_highvar %>%
  dplyr::select(gene,timepoint,log_exp) %>%
  pivot_wider(names_from = timepoint, values_from = log_exp) %>%
  as.data.frame()

row.names(highvar_log_wide) <- highvar_log_wide$gene
head(highvar_log_wide)
# correlate genes with each other
# As we not only focus on the trend, the value itself matters, we still use default setting for correlation (Pearson)
cor_matrix = cor(t(highvar_log_wide[, -1]))
dim(cor_matrix)
[1] 3000 3000

Edge selection

# In order to filter out the not meaning full relationships
# We use t distribution approximation, as for each correlation coeff (r), we can approximate a t statistics, under some arbitrary assumptions
n_daa = ncol(highvar_log_wide) -1

# deduplicate the lower part for the cor matrix
cor_matrix_upper_tri <- cor_matrix
cor_matrix_upper_tri[lower.tri(cor_matrix_upper_tri)] <- NA

# t = r*sqrt((n-2)/(1-r^2))
edge_table <- cor_matrix_upper_tri %>% 
  as.data.frame() %>% 
  mutate(from = row.names(cor_matrix)) %>% 
  pivot_longer(cols = !from, names_to = "to", values_to = "r") %>% 
  filter(is.na(r) == F) %>% 
  filter(from != to) %>% 
  mutate(t = r*sqrt((n_daa-2)/(1-r^2))) %>% 
  mutate(p.value = case_when(
    t > 0 ~ pt(t, df = n_daa-2, lower.tail = F),
    t <=0 ~ pt(t, df = n_daa-2, lower.tail = T)
  )) %>% 
  mutate(FDR = p.adjust(p.value, method = "fdr")) 

head(edge_table)
edge_table %>% 
  filter(r > 0) %>% 
  filter(FDR < 0.01) %>% 
  slice_min(order_by = abs(r), n = 10) # FDR cut off at 0.05, r > 0.9; cut off at 0.01, r > 0.97
# Visualize the distribution of r
edge_table %>% 
  ggplot(aes(x = r)) +
  geom_histogram(color = "white", bins = 100) +
  geom_vline(xintercept = 0.7, color = "tomato1", size = 1.2) +
  theme_classic() +
  theme(
    text = element_text(size = 14),
    axis.text = element_text(color = "black")
  )

ggsave("edge_table_distribution.svg", height = 4, width = 5, bg ="white")
ggsave("edge_table_distribution.png", height = 4, width = 5, bg ="white")

The more strict the cutoff, the “fewer” relationship will be used to contrust the co-expression network Looks like at r > 0.7 (red line), the distribution trails off rapidly.

# bait gene co-expression
edge_table %>%
  filter(str_detect(from,"EB107042") & str_detect(to, "CN943384") | str_detect(from,"CN943384") & str_detect(to, "EB107042") | 
           str_detect(from,"EB141951") & str_detect(to, "CN943384") | str_detect(from,"CN943384") & str_detect(to, "EB141951") | 
           str_detect(from,"EB107042") & str_detect(to, "EB141951") | str_detect(from,"EB141951") & str_detect(to, "EB107042"))

We can see that one of the bait gene seems not to be having that high relationship with the other two. According to the paper, we can see the trend from the 0 DAA to 14 DAA of EB107042 does vary from the others. So as recommended by the workflow, it’s still good to use 0.7 as a cut off.

edge_table_select = edge_table %>%
      filter(r >= 0.07)

Reason for not using p value cut off alone?

Why do I warn against determining cutoffs using p values alone? Because p value is a function of both effect size (r) and degrees of freedom (df). Experiments with larger df produces smaller p values given the same effect size. Let’s make a graph to illustrate that:

t_dist_example <- expand.grid(
  df = c(2, 5, 10, 50, 80, 100),
  r = c(0.2, 0.3, 0.5, 0.7, 0.8, 0.9, 0.99)
  ) %>% 
  mutate(t = r*sqrt((df-2)/(1-r^2))) %>% 
  mutate(p = pt(q = t, df = df, lower.tail = F))
  
t_dist_example %>% 
  ggplot(aes(x = r, y = -log10(p))) +
  geom_line(aes(group = df, color = as.factor(df)), 
            size = 1.1, alpha = 0.8) +
  geom_hline(yintercept = 2, color = "grey20", size = 1, linetype = 4) +
  labs(color = "df",
       caption = "dotted line: P = 0.01") +
  theme_classic() +
  theme(
    legend.position = c(0.2, 0.6),
    text = element_text(size = 14),
    axis.text = element_text(color = "black"),
    plot.caption = element_text(hjust = 0, size = 14)
  )
Warning: A numeric `legend.position` argument in `theme()` was deprecated in ggplot2 3.5.0.
ℹ Please use the `legend.position.inside` argument of `theme()` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
ggsave("t_dist_example.svg", height = 5, width = 6, bg ="white")
ggsave("t_dist_example.png", height = 5, width = 6, bg ="white")

As you can see, large size experiments (df = 80 or 100), you would reach p < 0.01 with r value between 0.2 and 0.4. However, for experiments with df at 5, you won’t get to p = 0.01 unless you have r values closer to 0.9.

So it’s really related to df. In order to disregard the effect, we can use bait genes to guid the cutoff. For now, I don’t have one.

Module Selection

Assign genes to different modules based on their correlation in between, that is, to detect the co-expressed genes.

node_table = data.frame(
    gene = c(edge_table_select$from, edge_table_select$to) %>% unique()
  )

head(node_table)
dim(node_table)
[1] 3000    1
my_network = graph_from_data_frame(
    edge_table_select,
    vertices = node_table,
    directed = F
  )

How to determine the resolution for cluster leiden

optimize_resolution = function(network, resolution) {
    modules = network %>% 
      cluster_leiden(resolution_parameter = resolution, 
                     objective_function = "modularity")
  
    parsed_modules = data.frame(
      gene_ID = names(membership(modules)),
      module = as.vector(membership(modules))
    )

    num_module_5 = parsed_modules %>%
      group_by(module) %>%
      count() %>%
      arrange(-n) %>%
      filter(n >= 5) %>%
      nrow() %>%
      as.numeric()

    num_genes_contained = parsed_modules %>%
      group_by(module) %>%
      count() %>%
      arrange(-n) %>%
      filter(n >= 5) %>%
      ungroup() %>%
      summarise(sum = sum(n)) %>%
      as.numeric()

    c(num_module_5, num_genes_contained)
}
optimization_results_list = purrr::map(
    .x = seq(from = 0.25, to = 5, by = 0.25),
    .f = optimize_resolution, 
    network = my_network
    )
Warning: The `resolution_parameter` argument of `cluster_leiden()` is deprecated as of igraph 2.1.0.
ℹ Please use the `resolution` argument instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
optimization_results_df = as.data.frame(do.call(cbind, optimization_results_list))
  
optimization_results = optimization_results_df %>%
    t() %>% 
    cbind(resolution = seq(from = 0.25, to = 5, by = 0.25)) %>% 
    as.data.frame() %>% 
    rename(num_module = V1, num_contained_gene = V2)

Optimize_num_module = optimization_results %>%
    ggplot(aes(x = resolution, y = num_module)) +
    geom_line(size = 1.1, alpha = 0.8, color = "dodgerblue2") +
    geom_point(size = 3, alpha = 0.7) +
    geom_vline(xintercept = 2, size = 1, linetype = 4) + # resolution_parameter vareis, start trying with 1.5
    labs(x = "resolution parameter", y = "num. modules\nw/ >=5 genes") +
    theme_classic() + theme(text = element_text(size = 14), axis.text = element_text(color = "black"))

Optimize_num_gene = optimization_results %>%
    ggplot(aes(x = resolution, y = num_contained_gene)) +
    geom_line(size = 1.1, alpha = 0.8, color = "violetred2") +
    geom_point(size = 3, alpha = 0.7) +
    geom_vline(xintercept = 2, size = 1, linetype = 4) +
    labs(x = "resolution parameter", y = "num. genes in\nmodules w/ >=5 genes") +
    theme_classic() + theme(text = element_text(size = 14), axis.text = element_text(color = "black"))

Optimize_num_module / Optimize_num_gene

ggsave("resolution.svg", height = 5, width = 6, bg ="white")
ggsave("resolution.png", height = 5, width = 6, bg ="white")

set.seed(987)

modules = cluster_leiden(my_network, resolution_parameter = 2, objective_function = "modularity")

my_network_modules = data.frame(
    gene = names(membership(modules)),
    module = as.vector(membership(modules))
  )

my_network_modules %>% 
  group_by(module) %>% 
  count() %>% 
  arrange(-n) %>% 
  filter(n >= 5) 

12 modules contain more than 5 genes, covering 2560 genes.

my_network_modules

DGE analysis

apple_exp_log_wide = apple_exp_log_long %>%
  dplyr::select(gene,timepoint,log_exp) %>%
  pivot_wider(names_from = timepoint, values_from = log_exp) %>%
  column_to_rownames("gene")

head(apple_exp_log_wide)
# manually calculate log2fc
# the reason for not using DESeq2 is they require raw count (integer) input
# the reason for not using limma is they require multiple replicates for each condition

log2_fc <- apple_exp_log_wide %>%
  mutate(
    log2FC_35_vs_60 = log2(`35 DAA` / `60 DAA`),  # cell division end vs starch accumulation
    log2FC_87_vs_132 = log2(`87 DAA` / `132 DAA`),  # starch accumulation vs ripening 
    log2FC_35_vs_132 = log2(`35 DAA` / `132 DAA`),  # cell division vs ripening
    log2FC_60_vs_87 = log2(`60 DAA` / `87 DAA`)  # starch accumulation vs end
  )

head(log2_fc)
ripening_genes = log2_fc %>%
  filter(log2FC_35_vs_132 < -1 & log2FC_87_vs_132 < -1 ) %>%
  rownames() # In both comparison, significantly upregulated in ripening stages,可能与果实成熟相关。这些基因可能参与调控果实质地、风味、色泽的变化。

starch_reg_genes = log2_fc %>%
  filter(abs(log2FC_60_vs_87) > 1) %>% # > 1: 60 > 87; < -1 60 < 87
  rownames()
set.seed(987)

library(pheatmap)

heatmap_data_ripen <- apple_exp_log_wide[ripening_genes, ]

# Save as SVG
# svglite("heatmap_ripening.svg", width = 5, height = 6)
# heatmap
pheatmap(
  heatmap_data_ripen, 
  cluster_rows = TRUE, 
  cluster_cols = FALSE, 
  show_rownames = TRUE, 
  scale = "row"
)

# dev.off()
set.seed(987)

library(pheatmap)

heatmap_data_starch <- apple_exp_log_wide[starch_reg_genes, ]
# svglite("heatmap_starch.svg", width = 5, height = 6)
# heatmap
pheatmap(
  heatmap_data_starch, 
  cluster_rows = TRUE, 
  cluster_cols = FALSE, 
  show_rownames = FALSE, 
  scale = "row"
)

# dev.off()

# many genes
pheatmap(
  apple_exp_log_wide,
  cluster_rows = FALSE, 
  cluster_cols = FALSE, 
  show_rownames = FALSE, 
  scale = "row"
)

sample_info = data.frame(
  sample = colnames(apple_exp_log_wide),
  group = c("T0", "T14", "T25", "T35", "T60", "T87", "T132", "T146")
)

sample_info$group <- factor(sample_info$group, levels = unique(sample_info$group))

Enrichment caculation for certain genes

# Calculate enrichment for modules function
# Make sure you got the module_info_df.
# The format of module info df should be a 2 columns tibble with first column named gene, second column named module.
# The interesting_genes part should be a set of genes.
calculate_module_enrichment = function(module_info_df, interesting_genes) {
  library(broom)
  library(tidyverse)

  df1 = module_info_df %>%
    group_by(module) %>%
    summarise(
      raw_count = n(),
    ) %>%
    ungroup()
  
  df2 = data.frame(gene = interesting_genes) %>%
    left_join(module_info_df, by = "gene") %>%
    group_by(module) %>%
    summarise(
      raw_count = n(),
    ) %>%
    ungroup()
  
  all_modules = unique(df1$module)

  result_df = tibble() 
  
  for (i in 1:length(all_modules)) {
    module = all_modules[i]

    raw_count_df1 = df1[df1$module == module, "raw_count"]
    raw_count_df2 = na.omit(df2[df2$module == module, "raw_count"])

    if (nrow(raw_count_df2) == 0) {
        raw_count_df2 = 0
    }
    total_count_df1 = sum(df1$raw_count)
    total_count_df2 = sum(df2$raw_count)
    
    contingency_table = matrix(c(sum(raw_count_df2), 
         sum(raw_count_df1), 
         total_count_df2 - sum(raw_count_df2),
         total_count_df1 - sum(raw_count_df1)),
       nrow = 2)
    
    fisher_test_result = tidy(fisher.test(contingency_table, alternative = "g"))
    
    percentage = paste(round(sum(raw_count_df2) / total_count_df2 * 100, 2), "% / ", 
                        round(sum(raw_count_df1) / total_count_df1 * 100, 2), "%", sep = "")
    
    temp_df = tibble(module = module,
                     raw_count = paste(raw_count_df2, raw_count_df1, sep = "/"),
                     percentage = percentage)
    
    temp_df = bind_cols(temp_df, fisher_test_result) 
    result_df = bind_rows(result_df, temp_df) 
  }
  
  return(result_df)
}
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyBBcHBsZSBkYXRhIHVzaW5nIFNpbXBsZVRpZHkgV29ya2Zsb3cKCkhlcmUgd2UgdXNlIGFub3RoZXIgZGF0YSBzZXQgdHJ5aW5nIHRvIGdvIHRocm91Z2ggdGhlIHNhbWUgd29ya2Zsb3cuCgojIFBhY2thZ2UgcmVxdWlyZW1lbnQKCmBgYHtyfQpsaWJyYXJ5KHN2Z2xpdGUpCmxpYnJhcnkobGltbWEpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoaWdyYXBoKQpsaWJyYXJ5KGdncmFwaCkKCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkodmlyaWRpcykKYGBgCgojIERhdGEgcmVzb3VyY2UKCk1hbHVzIGRvbWVzdGljYS4gVGhlIGRhdGEgZnJhbWUgY29udGFpbnMgbWljcm9hcnJheSBnZW5lIGV4cHJlc3Npb24gZGF0YSwgd2l0aCBlYWNoIHJvdyBjb3JyZXNwb25kaW5nIHRvIGEgZ2VuZSBhbmQgZWFjaCBjb2x1bW4gcmVwcmVzZW50aW5nIGV4cHJlc3Npb24gbGV2ZWxzIGF0IGRpZmZlcmVudCB0aW1lIHBvaW50cy4gVGhlIGRhdGEgaW5jbHVkZXMgdGhlIG1lYW4gKE1lYW4pLCBudW1iZXIgb2YgZGF0YSBwb2ludHMgKG4pLCBhbmQgc3RhbmRhcmQgZXJyb3IgKFNFKS4gRm9yIHRoZSAwIERBQSB0aW1lIHBvaW50LCBvbmx5IG9uZSBiaW9sb2dpY2FsIHJlcGxpY2F0ZSB3YXMgc2FtcGxlZC4gRm9yIG90aGVyIHRpbWUgcG9pbnRzLCB0aGUgZGF0YSBpcyBiYXNlZCBvbiB0d28gYmlvbG9naWNhbCByZXBsaWNhdGVzIGFuZCB0d28gdGVjaG5pY2FsIHJlcGxpY2F0ZXMuIEluIHNvbWUgY2FzZXMsIGRhdGEgcG9pbnRzIHdlcmUgZXhjbHVkZWQgZm9yIHRlY2huaWNhbCByZWFzb25zLCBzbyB0aGUgbWVhbiBhbmQgc3RhbmRhcmQgZXJyb3IgYXJlIG9ubHkgY2FsY3VsYXRlZCBiYXNlZCBvbiB0aGUgcmVtYWluaW5nIGRhdGEuCgpgYGB7cn0KYXBwbGVfZXhwID0gcmVhZF9leGNlbCgiQXBwbGVfREFBX2V4cHJlc3Npb24ueGxzIixjb2xfdHlwZXMgPSAidGV4dCIpCmRpbShhcHBsZV9leHApCmBgYCAgIAoKVGhlcmUgYXJlIDE1OTg3IGdlbmVzIGFuZCA4IHRpbWUgcG9pbnQsIERBQSBzdGFuZHMgZm9yIERheXMgQWZ0ZXIgQW50aGVzaXMuCgpgYGB7cn0KIyBnZW5lcmFsIGNoZWNrIG9uIHRoZSBkZiBpbmZvCgphcHBsZV9leHAgJT4lCiAgbXV0YXRlKGFjcm9zcyhlbmRzX3dpdGgoIk1lYW4iKSwgYXMubnVtZXJpYykpICU+JQogIHNlbGVjdChlbmRzX3dpdGgoIk1lYW4iKSkgJT4lCiAgc3VtbWFyeSgpCmBgYAoKVXN1YWxseSwgd2hlbiB0aGUgbWlzc2luZyB2YWx1ZXMgd2VyZSBjYXVzZWQgYnkgdmVyeSBsb3cgZXhwcmVzc2lvbiB2YWx1ZXMgb3Igbm90IGRldGVjdGVkLCB0aGV5IHdlcmUgZ2VuZXJhbGx5IHJlcGxhY2VkIGJ5IDAuIApIb3dldmVyLCBhY2NvcmRpbmcgdG8gdGhlIGFydGljbGUsIHRoZSBOQSBwYXJ0IG9mIHRoaXMgZGF0YSBmcmFtZSB3YXMgZXhjbHVkZWQgZm9yIHRlY2huaWNhbCByZWFzb25zLCBzbyBoZXJlIHdlIGVtcGxveWVkIGFuIGV4Y2x1c2lvbiBzdHJhdGVneSwgaS5lLiwgd2UgZGlkIG5vdCBzZWxlY3QgdGhlIGdlbmVzIGNvbnRhaW5pbmcgdGhlIG1pc3NpbmcgdmFsdWVzIGZvciB0aGUgc3Vic2VxdWVudCBhbmFseXNpcy4KCgpgYGB7cn0KYXBwbGVfZXhwID0gYXBwbGVfZXhwICU+JQogIG11dGF0ZShhY3Jvc3MoZW5kc193aXRoKCJNZWFuIiksIGFzLm51bWVyaWMpKSAlPiUKICBzZWxlY3QoYEdlbmJhbmsgbnVtYmVyYCxlbmRzX3dpdGgoIk1lYW4iKSkgJT4lICMgU0UgYW5kIG4gaXMgZ29vZCB0byBrbm93LCBidXQgbm90IGZvciBzdWJzZXF1ZW50IGFuYWx5c2lzCiAgZHJvcF9uYSgpCgpkaW0oYXBwbGVfZXhwKSAjIDgzNiBnZW5lcyBleGNsdWRlZCwgNSUuIFZhbGlkYXRpb24gcmVxdWlyZWQgbGF0ZXIuCmBgYAoKYGBge3IsZXZhbD1GQUxTRX0Kd3JpdGUudGFibGUoYXBwbGVfZXhwICU+JQogIGRpc3RpbmN0KGBHZW5iYW5rIG51bWJlcmApLCAiZ2VuYmFua19hcHBsZS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSwgY29sLm5hbWVzID0gRkFMU0UsIHF1b3RlID0gRkFMU0UpCmBgYAoKCiMgUENBCgpgYGB7cn0KCmdncGxvdChhcHBsZV9leHAsIGFlcyh4ID0gYDAgREFBIE1lYW5gKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAzMCkgCgpnZ3NhdmUoImhpc3RfYmVmb3JlLnN2ZyIsIGhlaWdodCA9IDMsIHdpZHRoID0gNCwgYmcgPSJ3aGl0ZSIpCmdnc2F2ZSgiaGlzdF9iZWZvcmUucG5nIiwgaGVpZ2h0ID0gMywgd2lkdGggPSA0LCBiZyA9IndoaXRlIikKIyBkZXYub2ZmKCkKYGBgCgoKYGBge3J9CmFwcGxlX2V4cF9sb2cgPSBhcHBsZV9leHAgJT4lCiAgbXV0YXRlKGFjcm9zcyhlbmRzX3dpdGgoIk1lYW4iKSwgfiBsb2cxMCguICsgMSkpKSAjIGxvZyB0cmFuc2Zvcm0gdGhlIGRmCgpnZ3Bsb3QoYXBwbGVfZXhwX2xvZywgYWVzKHggPSBgMCBEQUEgTWVhbmApKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDMwKSAjIFRoZSBkYXRhIGRpc3RyaWJ1dGlvbiBpcyBzdGlsbCBza2V3ZWQgZHVlIHRvIHRoZSBsb3cgZXhwcmVzc2lvbiB2YWx1ZXMgb2YgbW9zdCBnZW5lcywgYnV0IGl0IGlzIGJldHRlciB0aGFuIHVzaW5nIHJhdyB2YWx1ZXMKCmdnc2F2ZSgiaGlzdF9hZnRlci5zdmciLCBoZWlnaHQgPSAzLCB3aWR0aCA9IDQsIGJnID0id2hpdGUiKQpnZ3NhdmUoImhpc3RfYWZ0ZXIucG5nIiwgaGVpZ2h0ID0gMywgd2lkdGggPSA0LCBiZyA9IndoaXRlIikKYGBgCgoKYGBge3J9CmFwcGxlX3BjYSA9IHByY29tcCh0KGFwcGxlX2V4cF9sb2dbLCAtMV0pKQphcHBsZV9wY2FfaW1wID0gYXMuZGF0YS5mcmFtZSh0KHN1bW1hcnkoYXBwbGVfcGNhKSRpbXBvcnRhbmNlKSkKYXBwbGVfcGNhX2Nvb3JkID0gYXBwbGVfcGNhJHhbLCAxOjhdICU+JSAjIFRha2UgdGhlIGFsbCA4IFBDcwogIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgbXV0YXRlKHRpbWVwb2ludCA9IHJvdy5uYW1lcyguKSkgIyByb3duYW1lcyB0byBjb2wKYXBwbGVfcGNhX2Nvb3JkCmBgYAoKQWNjb3JkaW5nIHRvIHRoZSBwYXBlciwgREFBIGFuZCB0aGUgZnJ1aXQgZGV2ZWxvcG1lbnQgc3RhZ2UgaGF2ZSBhIGNlcnRhaW4gY29ycmVzcG9uZGluZyByZWxhdGlvbnNoaXA6CgowLTM1IERBQSA6IENlbGwgZGl2aXNpb24KNjAtODcgREFBIDogU3RhcmNoIGFjY3VtdWxhdGlvbgoxMzItMTQ2IERBIDogUmlwZW5pbmcKCkFzIHdlIGRvbid0IGhhdmUgbWFueSBjb21iaW5hdGlvbiBvZiB0aGUgbGlicmFyaWVzLCB3ZSB3b24ndCBleHBlY3QgYSBzdHJvbmcgZXhwbGFuYXRpb24gYnkgc3RhZ2UgYnV0IGl0IHdvcnRoIGEgbG9vay4KCmBgYHtyfQojIG11dGF0ZSBhIHN0YWdlIGNvbHVtbgphcHBsZV9wY2FfY29vcmQgPSBhcHBsZV9wY2FfY29vcmQgJT4lCiAgbXV0YXRlKHN0YWdlID0gY2FzZV93aGVuKAogICAgdGltZXBvaW50ICVpbiUgYygiMCBEQUEgTWVhbiIsICIxNCBEQUEgTWVhbiIsICIyNSBEQUEgTWVhbiIsICIzNSBEQUEgTWVhbiIpIH4gIkNlbGwgZGl2aXNpb24iLAogICAgdGltZXBvaW50ICVpbiUgYygiNjAgREFBIE1lYW4iLCAiODcgREFBIE1lYW4iKSB+ICJTdGFyY2ggYWNjdW11bGF0aW9uIiwKICAgIHRpbWVwb2ludCAlaW4lIGMoIjEzMiBEQUEgTWVhbiIsICIxNDYgREFBIE1lYW4iKSB+ICJSaXBlbmluZyIsCiAgICBUUlVFIH4gIlVua25vd24iCiAgKSkgJT4lCiAgbXV0YXRlKHRpbWVwb2ludCA9IHN0cl9yZXBsYWNlKHRpbWVwb2ludCwgIiBNZWFuIiwgIiIpKSAjIFJlbW92ZSB0aGUgIk1lYW4iIHdvcmQgaW4gdGltZXBvaW50IGNvbAoKYXBwbGVfcGNhX2Nvb3JkICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBQQzEsIHkgPSBQQzIpKSArCiAgZ2VvbV9wb2ludChhZXMoZmlsbCA9IHN0YWdlKSwgY29sb3IgPSAiZ3JleTIwIiwgc2hhcGUgPSAyMSwgc2l6ZSA9IDMsIGFscGhhID0gMC44KSArCiAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIkNlbGwgZGl2aXNpb24iID0gIiMxYjllNzciLCAiU3RhcmNoIGFjY3VtdWxhdGlvbiIgPSAiI2Q5NWYwMiIsICJSaXBlbmluZyIgPSAiIzc1NzBiMyIpKSArCiAgbGFicyh4ID0gcGFzdGUoIlBDMSAoIiwgYXBwbGVfcGNhX2ltcFsxLCAyXSAlPiUgc2lnbmlmKDMpKjEwMCwgIiUgb2YgVmFyaWFuY2UpIiwgc2VwID0gIiIpLCAKICAgICAgIHkgPSBwYXN0ZSgiUEMyICgiLCBhcHBsZV9wY2FfaW1wWzIsIDJdICU+JSBzaWduaWYoMykqMTAwLCAiJSBvZiBWYXJpYW5jZSkiLCAiICAiLCBzZXAgPSAiIiksCiAgICAgICBmaWxsID0gInN0YWdlIikgKyAgCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoCiAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9IDE0KSwKICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJibGFjayIpCiAgKQoKZ2dzYXZlKCJwY2FfYXBwbGUuc3ZnIiwgaGVpZ2h0ID0gNCwgd2lkdGggPSA4LCBiZyA9IndoaXRlIikKZ2dzYXZlKCJwY2FfYXBwbGUucG5nIiwgaGVpZ2h0ID0gNCwgd2lkdGggPSA4LCBiZyA9IndoaXRlIikKYGBgCgpTaW5jZSBQQ0EgaXMgcGVyZm9ybWVkIGJhc2VkIG9uIGdlbmUgZXhwcmVzc2lvbiBkYXRhIGF0IGRpZmZlcmVudCB0aW1lIHBvaW50cywgdGhpcyBuYXR1cmFsbHkgY2F1c2VzIGRpZmZlcmVudCBkZXZlbG9wbWVudGFsIHN0YWdlcyB0byBzaG93IHNvbWUgc2VwYXJhdGlvbiBpbiBQQ0Egc3BhY2UuIApIb3dldmVyLCBQQ0EgaXRzZWxmIGlzIHVuc3VwZXJ2aXNlZCBhbmQgZG9lcyBub3Qga25vdyB0aGUgb3JkZXIgb2YgdGltZSBwb2ludHMsIHdoaWxlIHdlIHNlZSB0aGUgb3JkZXJpbmcgb2YgUEMxIGlzIGNvbnNpc3RlbnQgd2l0aCB0aGUgb3JkZXIgb2YgdGltZSBwb2ludHMsIGFuZCB0aGUgaW50ZXJ2YWwgYmV0d2VlbiBkaWZmZXJlbnQgc3RhZ2VzIGlzIG9idmlvdXMuIFRoaXMgc3VnZ2VzdHMgdGhhdCB0aGUgY2xvc2VyIHRoZSB0aW1lIGlzIHRvIGxhdGUgZGV2ZWxvcG1lbnQsIHRoZSBleHByZXNzaW9uIHBhdHRlcm4gd2lsbCBncmFkdWFsbHkgY2hhbmdlLCBhbmQgUENBIGhhcyBjYXB0dXJlZCB0aGlzIGZlYXR1cmUuCgojIEJhaXQgZ2VuZQoKQXMgdGhlIHJvd3MgaW4gdGhlIG1hdHJpeCBhcmUgbm90IGFjdHVhbCBnZW5lIG5hbWVzLCBhbmQgSSBkaWRuJ3QgbWFwIHRoZSBwcm9iZXMgdG8gdGhlIGdlbmVzLCBpdCdzIGhhcmQgdG8gZ2V0IHRoZSBiYWl0IGdlbmVzLiBIb3dldmVyLCB0aGUgcGFwZXIgcHJvdmlkZSB0aHJlZSBjb3JlIGNlbGwgY3ljbGUgZ2VuZXMsIHdoaWNoIGFyZToKRUIxMDcwNDI6IENES0IxOzIgaG9tb2xvZ3VlLApDTjk0MzM4NDogQ0RLQjI7MiBob21vbG9ndWUsCkVCMTQxOTUxOiBDS1MxIGhvbW9sb2d1ZQoKV2UnbGwgdXNlIHRoaXMgdGhyZWUgdG8gc2VydmUgYXMgYmFpdCBnZW5lCgpgYGB7cn0KYmFpdF9nZW5lcyA9IGMoIkVCMTA3MDQyIiwiQ045NDMzODQiLCJFQjE0MTk1MSIpCmBgYAoKCiMgRHVwbGljYXRlZCBnZW5lcwoKYGBge3J9CmFwcGxlX2V4cF9sb2cgJT4lCiAgZGltKCkKYGBgCgpgYGB7cn0KYXBwbGVfZXhwX2xvZyAlPiUKICBkaXN0aW5jdChgR2VuYmFuayBudW1iZXJgKSAlPiUKICBkaW0oKQpgYGAKCldlIG5vdGljZSB0aGF0IDE1MTUxID4gMTQ5MDIuIFRoZXJlIG11c3QgYmUgZHVwbGljYXRlZCBjb21iaW5hdGlvbjoKClRoZSByZWFzb24gZm9yIHRoaXMgImR1cGxpY2F0aW9uIiBpcyB0aGF0IHdlIGRlbGV0ZWQgdGhlIEVTVHMgKEV4cHJlc3NlZCBTZXF1ZW5jZSBUYWdzKSBjb2x1bW4gYXQgdGhlIGJlZ2lubmluZyBvZiB0aGUgZGF0YSBwcm9jZXNzaW5nIGFuZCB1c2VkIHRoZSBHZW5iYW5rIG51bWJlciBhcyB0aGUgZ2VuZSBpZGVudGlmaWNhdGlvbi4KClRoaXMgbWF5IHJlcHJlc2VudCBkaWZmZXJlbnQgZnJhZ21lbnRzIG9yIHRyYW5zY3JpcHRzIG9mIHRoZSBzYW1lIGdlbmUuIFdlIHNob3VsZCBhbHRlciBiYXNlZCBvbiB0aGUgZ2VuZSBpZGVudGlmaWNhdGlvbiB0byBkaWZmZXJlbnRpYXRlIHRoZW0uCgpgYGB7cn0KYXBwbGVfZXhwX2xvZyA9IGFwcGxlX2V4cF9sb2cgJT4lCiAgZ3JvdXBfYnkoYEdlbmJhbmsgbnVtYmVyYCkgJT4lCiAgbXV0YXRlKGdlbmUgPSBjYXNlX3doZW4oICMgbXV0YXRlIGEgbmV3IGNvbCAKICAgIG4oKSA+IDEgfiBwYXN0ZTAoYEdlbmJhbmsgbnVtYmVyYCwgIl8iLCByb3dfbnVtYmVyKCkpLCAjICJkdXBsaWNhdGVzIiB3aWxsIGhhdmUgbW9yZSB0aGFuIDEgcm93LiBBZGQgYSBzeW1ib2wuCiAgICBUUlVFIH4gYEdlbmJhbmsgbnVtYmVyYCAjIGtlZXAgdGhlIHNhbWUgZm9yIHRob3NlIG5vdF9kdXBsaWNhdGVkCiAgKSkgJT4lCiAgdW5ncm91cCgpICMgJT4lCiAgIyBmaWx0ZXIoZ2VuZSAhPSBgR2VuYmFuayBudW1iZXJgKSAjIGhvdyB0byBjaGVjayBmb3IgdGhlIGFsdGVyZWQgcm93cwpgYGAKCgojIEdlbmUgc2VsZWN0aW9uCgpgYGB7cn0KIyBsb25nIHZlcnNpb24KYXBwbGVfZXhwX2xvZ19sb25nID0gYXBwbGVfZXhwX2xvZyAlPiUKICByZW5hbWVfd2l0aCh+IGdzdWIoIiBNZWFuIiwgIiIsIC4pLCBjb250YWlucygiREFBIikpICU+JSAjIFJlbW92ZSB0aGUgIk1lYW4iIGluIGNvbG5hbWVzCiAgcGl2b3RfbG9uZ2VyKAogICAgY29scyA9IHN0YXJ0c193aXRoKCIwIERBQSIpOnN0YXJ0c193aXRoKCIxNDYgREFBIiksIAogICAgbmFtZXNfdG8gPSAidGltZXBvaW50IiwgCiAgICB2YWx1ZXNfdG8gPSAibG9nX2V4cCIKICApIApgYGAKCkFzIHdlIGtub3csIHRoZSBudW1iZXIgb2YgY29ycmVsYXRpb25zIHNjYWxlcyB0byB0aGUgc3F1YXJlIG9mIG51bWJlciBvZiBnZW5lcy4KSW4gb3JkZXIgdG8gY2FsY3VsYXRlIGdlbmUgY29ycmVsYXRpb24gYmV0d2VlbiBlYWNoIG90aGVyLCAxNTAwMCsgZ2VuZXMgYXJlIHRvbyBtdWNoLiAKV2UgY2FuIHNlbGVjdCBvbmx5IHRoZSBoaWdoIHZhcmlhbmNlIGdlbmVzLCBhcyBhIGdlbmUgaXMgdW5saWtlbHkgdG8gYmUgaW52b2x2ZWQgaW4gYSBwYXJ0aWN1bGFyIHN0YWdlIGlmIGl0J3MgZXhwcmVzc2VkIGF0IGEgc2ltaWxhciBsZXZlbCBhY3Jvc3MgYWxsIHRpbWVwb2ludHMuCgoKYGBge3J9CiMgQ2FsY3VsYXRlIHRoZSByYW5rCmFwcGxlX3Zhcl9yYW5rID0gYXBwbGVfZXhwX2xvZ19sb25nICU+JSAKICBncm91cF9ieShnZW5lKSAlPiUgCiAgc3VtbWFyaXNlKHZhciA9IHZhcihsb2dfZXhwKSkgJT4lICMgY2FsY3VsYXRlIHRoZSB2YXJpYW5jZSBmb3IgZWFjaCBnZW5lCiAgdW5ncm91cCgpICU+JSAKICBtdXRhdGUocmFuayA9IHJhbmsodmFyLCB0aWVzLm1ldGhvZCA9ICJhdmVyYWdlIikpICMgcmFuayB0aGUgZ2VuZXMKCmhlYWQoYXBwbGVfdmFyX3JhbmspCmBgYAoKYGBge3J9CiMgSWYgd2UgdGFrZSB0aGUgdG9wIDEvMyBvZiB0aGUgaGlnaGVzdCB2YXJpYW5jZSBnZW5lcwpoaWdoX3Zhcl9hcHBsZSA9IGFwcGxlX2V4cF9sb2dfbG9uZyAlPiUgCiAgZ3JvdXBfYnkoZ2VuZSkgJT4lIAogIHN1bW1hcmlzZSh2YXIgPSB2YXIobG9nX2V4cCkpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGZpbHRlcih2YXIgPiBxdWFudGlsZSh2YXIsIDAuNjY3KSkgCgojIEFuZCB0YWtlIHRoZSB0b3AgMzAwMApoaWdoX3Zhcl9hcHBsZTMwMDAgPSBoaWdoX3Zhcl9hcHBsZSAlPiUgCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gdmFyLCBuID0gMzAwMCkgCmBgYAoKYGBge3J9CmJhaXRfdmFyID0gYXBwbGVfdmFyX3JhbmsgJT4lCiAgZmlsdGVyKGdlbmUgJWluJSBiYWl0X2dlbmVzKSAKYGBgCgoKYGBge3J9CiMgQ2hlY2sgd2hldGhlciB0b3AgMzAwMCBjYW4gcmVwcmVzZW50IHRoZSBnZW5lcyBvZiBoaWdoZXN0IHZhcmlhbmNlCmFwcGxlX3Zhcl9yYW5rICU+JSAKICBnZ3Bsb3QoYWVzKHggPSB2YXIsIHkgPSByYW5rKSkgKwogICBnZW9tX3JlY3QoIAogICAgeG1heCA9IG1heChoaWdoX3Zhcl9hcHBsZTMwMDAkdmFyKSwgCiAgICB4bWluID0gbWluKGhpZ2hfdmFyX2FwcGxlMzAwMCR2YXIpLAogICAgeW1heCA9IG5yb3coYXBwbGVfdmFyX3JhbmspLAogICAgeW1pbiA9IG5yb3coYXBwbGVfdmFyX3JhbmspIC0gMzAwMCwKICAgIGZpbGwgPSAiZG9kZ2VyYmx1ZTIiLCBhbHBoYSA9IDAuMgogICAgKSArCiAgZ2VvbV9obGluZSgKICAgIGRhdGEgPSBiYWl0X3ZhciwgYWVzKHlpbnRlcmNlcHQgPSByYW5rKSwKICAgIGNvbG9yID0gInRvbWF0bzEiLCBzaXplID0gMC44LCBhbHBoYSA9IDAuNQogICkgKwogIGdlb21fdmxpbmUoCiAgICBkYXRhID0gYmFpdF92YXIsIGFlcyh4aW50ZXJjZXB0ID0gdmFyKSwgCiAgICBjb2xvciA9ICJ0b21hdG8xIiwgc2l6ZSA9IDAuOCwgYWxwaGEgPSAwLjUKICApICsgCiAgZ2VvbV9saW5lKHNpemUgPSAxLjEpICsKICBsYWJzKHkgPSAicmFuayIsCiAgICAgICB4ID0gInZhcmlhbmNlIiwKICAgICAgIGNhcHRpb24gPSAiQmx1ZSBib3ggPSB0b3AgMzAwMCBoaWdoIHZhciBnZW5lcy4iKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICB0aGVtZSgKICAgIHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwKICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJibGFjayIpLAogICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMCkKICApCgpnZ3NhdmUoImhpZ2h2YXIzMDAwLnN2ZyIsIGhlaWdodCA9IDQsIHdpZHRoID0gNSwgYmcgPSJ3aGl0ZSIpCmdnc2F2ZSgiaGlnaHZhcjMwMDAucG5nIiwgaGVpZ2h0ID0gNCwgd2lkdGggPSA1LCBiZyA9IndoaXRlIikKYGBgCgpJdCBsb29rcyBsaWtlIGlmIHdlIHRha2UgdGhlIHRvcCAzMDAwIGdlbmVzLCBpdCB0YWtlcyBwcmV0dHkgbXVjaCB0aGUgZW50aXJlIHVwcGVyIGVsYm93IG9mIHRoZSBncmFwaC4gQW5kIGFsbCB0aGUgYmFpdCBnZW5lcyBhcmUganVzdCBpbmNsdWRlZC4KCgpgYGB7cn0KIyBTZWxlY3QgaGlnaCB2YXIgZ2VuZQphcHBsZV9leHBfbG9nX2hpZ2h2YXIgPSBhcHBsZV9leHBfbG9nX2xvbmcgJT4lIAogIGZpbHRlcihnZW5lICVpbiUgaGlnaF92YXJfYXBwbGUzMDAwJGdlbmUpCmRpbShhcHBsZV9leHBfbG9nX2hpZ2h2YXIpCmBgYAoKCmBgYHtyLGV2YWw9RkFMU0V9CmZpbHRlcmVkX2dlbmVzIDwtIGFwcGxlX2V4cF9sb2dfaGlnaHZhciAlPiUKICBkaXN0aW5jdChgR2VuYmFuayBudW1iZXJgKQoKd3JpdGUudGFibGUoZmlsdGVyZWRfZ2VuZXMsICJoaWdoX3Zhcl9hcHBsZS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSwgY29sLm5hbWVzID0gRkFMU0UsIHF1b3RlID0gRkFMU0UpCmBgYAoKCiMgR2VuZS13aXNlIGNvcnJlbGF0aW9uCgpgYGB7cn0KIyBwaXZvdCB3aWRlciBmb3IgYXMgaW5wdXQgZm9yIGNvcnJlbGF0aW9uCmhpZ2h2YXJfbG9nX3dpZGUgPSBhcHBsZV9leHBfbG9nX2hpZ2h2YXIgJT4lCiAgZHBseXI6OnNlbGVjdChnZW5lLHRpbWVwb2ludCxsb2dfZXhwKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdGltZXBvaW50LCB2YWx1ZXNfZnJvbSA9IGxvZ19leHApICU+JQogIGFzLmRhdGEuZnJhbWUoKQoKcm93Lm5hbWVzKGhpZ2h2YXJfbG9nX3dpZGUpIDwtIGhpZ2h2YXJfbG9nX3dpZGUkZ2VuZQpoZWFkKGhpZ2h2YXJfbG9nX3dpZGUpCmBgYAoKYGBge3J9CiMgY29ycmVsYXRlIGdlbmVzIHdpdGggZWFjaCBvdGhlcgojIEFzIHdlIG5vdCBvbmx5IGZvY3VzIG9uIHRoZSB0cmVuZCwgdGhlIHZhbHVlIGl0c2VsZiBtYXR0ZXJzLCB3ZSBzdGlsbCB1c2UgZGVmYXVsdCBzZXR0aW5nIGZvciBjb3JyZWxhdGlvbiAoUGVhcnNvbikKY29yX21hdHJpeCA9IGNvcih0KGhpZ2h2YXJfbG9nX3dpZGVbLCAtMV0pKQpkaW0oY29yX21hdHJpeCkKYGBgCgojIEVkZ2Ugc2VsZWN0aW9uIAoKYGBge3J9CiMgSW4gb3JkZXIgdG8gZmlsdGVyIG91dCB0aGUgbm90IG1lYW5pbmcgZnVsbCByZWxhdGlvbnNoaXBzCiMgV2UgdXNlIHQgZGlzdHJpYnV0aW9uIGFwcHJveGltYXRpb24sIGFzIGZvciBlYWNoIGNvcnJlbGF0aW9uIGNvZWZmIChyKSwgd2UgY2FuIGFwcHJveGltYXRlIGEgdCBzdGF0aXN0aWNzLCB1bmRlciBzb21lIGFyYml0cmFyeSBhc3N1bXB0aW9ucwpuX2RhYSA9IG5jb2woaGlnaHZhcl9sb2dfd2lkZSkgLTEKCiMgZGVkdXBsaWNhdGUgdGhlIGxvd2VyIHBhcnQgZm9yIHRoZSBjb3IgbWF0cml4CmNvcl9tYXRyaXhfdXBwZXJfdHJpIDwtIGNvcl9tYXRyaXgKY29yX21hdHJpeF91cHBlcl90cmlbbG93ZXIudHJpKGNvcl9tYXRyaXhfdXBwZXJfdHJpKV0gPC0gTkEKCiMgdCA9IHIqc3FydCgobi0yKS8oMS1yXjIpKQplZGdlX3RhYmxlIDwtIGNvcl9tYXRyaXhfdXBwZXJfdHJpICU+JSAKICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogIG11dGF0ZShmcm9tID0gcm93Lm5hbWVzKGNvcl9tYXRyaXgpKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAhZnJvbSwgbmFtZXNfdG8gPSAidG8iLCB2YWx1ZXNfdG8gPSAiciIpICU+JSAKICBmaWx0ZXIoaXMubmEocikgPT0gRikgJT4lIAogIGZpbHRlcihmcm9tICE9IHRvKSAlPiUgCiAgbXV0YXRlKHQgPSByKnNxcnQoKG5fZGFhLTIpLygxLXJeMikpKSAlPiUgCiAgbXV0YXRlKHAudmFsdWUgPSBjYXNlX3doZW4oCiAgICB0ID4gMCB+IHB0KHQsIGRmID0gbl9kYWEtMiwgbG93ZXIudGFpbCA9IEYpLAogICAgdCA8PTAgfiBwdCh0LCBkZiA9IG5fZGFhLTIsIGxvd2VyLnRhaWwgPSBUKQogICkpICU+JSAKICBtdXRhdGUoRkRSID0gcC5hZGp1c3QocC52YWx1ZSwgbWV0aG9kID0gImZkciIpKSAKCmhlYWQoZWRnZV90YWJsZSkKYGBgCgoKYGBge3J9CmVkZ2VfdGFibGUgJT4lIAogIGZpbHRlcihyID4gMCkgJT4lIAogIGZpbHRlcihGRFIgPCAwLjAxKSAlPiUgCiAgc2xpY2VfbWluKG9yZGVyX2J5ID0gYWJzKHIpLCBuID0gMTApICMgRkRSIGN1dCBvZmYgYXQgMC4wNSwgciA+IDAuOTsgY3V0IG9mZiBhdCAwLjAxLCByID4gMC45NwpgYGAKCgpgYGB7cn0KIyBWaXN1YWxpemUgdGhlIGRpc3RyaWJ1dGlvbiBvZiByCmVkZ2VfdGFibGUgJT4lIAogIGdncGxvdChhZXMoeCA9IHIpKSArCiAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPSAid2hpdGUiLCBiaW5zID0gMTAwKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMC43LCBjb2xvciA9ICJ0b21hdG8xIiwgc2l6ZSA9IDEuMikgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgdGhlbWUoCiAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksCiAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3IgPSAiYmxhY2siKQogICkKCmdnc2F2ZSgiZWRnZV90YWJsZV9kaXN0cmlidXRpb24uc3ZnIiwgaGVpZ2h0ID0gNCwgd2lkdGggPSA1LCBiZyA9IndoaXRlIikKZ2dzYXZlKCJlZGdlX3RhYmxlX2Rpc3RyaWJ1dGlvbi5wbmciLCBoZWlnaHQgPSA0LCB3aWR0aCA9IDUsIGJnID0id2hpdGUiKQpgYGAKClRoZSBtb3JlIHN0cmljdCB0aGUgY3V0b2ZmLCB0aGUgImZld2VyIiByZWxhdGlvbnNoaXAgd2lsbCBiZSB1c2VkIHRvIGNvbnRydXN0IHRoZSBjby1leHByZXNzaW9uIG5ldHdvcmsKTG9va3MgbGlrZSBhdCByID4gMC43IChyZWQgbGluZSksIHRoZSBkaXN0cmlidXRpb24gdHJhaWxzIG9mZiByYXBpZGx5LgoKYGBge3J9CiMgYmFpdCBnZW5lIGNvLWV4cHJlc3Npb24KZWRnZV90YWJsZSAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChmcm9tLCJFQjEwNzA0MiIpICYgc3RyX2RldGVjdCh0bywgIkNOOTQzMzg0IikgfCBzdHJfZGV0ZWN0KGZyb20sIkNOOTQzMzg0IikgJiBzdHJfZGV0ZWN0KHRvLCAiRUIxMDcwNDIiKSB8IAogICAgICAgICAgIHN0cl9kZXRlY3QoZnJvbSwiRUIxNDE5NTEiKSAmIHN0cl9kZXRlY3QodG8sICJDTjk0MzM4NCIpIHwgc3RyX2RldGVjdChmcm9tLCJDTjk0MzM4NCIpICYgc3RyX2RldGVjdCh0bywgIkVCMTQxOTUxIikgfCAKICAgICAgICAgICBzdHJfZGV0ZWN0KGZyb20sIkVCMTA3MDQyIikgJiBzdHJfZGV0ZWN0KHRvLCAiRUIxNDE5NTEiKSB8IHN0cl9kZXRlY3QoZnJvbSwiRUIxNDE5NTEiKSAmIHN0cl9kZXRlY3QodG8sICJFQjEwNzA0MiIpKQpgYGAKCldlIGNhbiBzZWUgdGhhdCBvbmUgb2YgdGhlIGJhaXQgZ2VuZSBzZWVtcyBub3QgdG8gYmUgaGF2aW5nIHRoYXQgaGlnaCByZWxhdGlvbnNoaXAgd2l0aCB0aGUgb3RoZXIgdHdvLiBBY2NvcmRpbmcgdG8gdGhlIHBhcGVyLCB3ZSBjYW4gc2VlIHRoZSB0cmVuZCBmcm9tIHRoZSAwIERBQSB0byAxNCBEQUEgb2YgRUIxMDcwNDIgZG9lcyB2YXJ5IGZyb20gdGhlIG90aGVycy4gU28gYXMgcmVjb21tZW5kZWQgYnkgdGhlIHdvcmtmbG93LCBpdCdzIHN0aWxsIGdvb2QgdG8gdXNlIDAuNyBhcyBhIGN1dCBvZmYuCgpgYGB7cn0KZWRnZV90YWJsZV9zZWxlY3QgPSBlZGdlX3RhYmxlICU+JQogICAgICBmaWx0ZXIociA+PSAwLjA3KQpgYGAKCgojIyBSZWFzb24gZm9yIG5vdCB1c2luZyBwIHZhbHVlIGN1dCBvZmYgYWxvbmU/CldoeSBkbyBJIHdhcm4gYWdhaW5zdCBkZXRlcm1pbmluZyBjdXRvZmZzIHVzaW5nIHAgdmFsdWVzIGFsb25lPyAKQmVjYXVzZSBwIHZhbHVlIGlzIGEgZnVuY3Rpb24gb2YgYm90aCBlZmZlY3Qgc2l6ZSAocikgYW5kIGRlZ3JlZXMgb2YgZnJlZWRvbSAoZGYpLiAKRXhwZXJpbWVudHMgd2l0aCBsYXJnZXIgZGYgcHJvZHVjZXMgc21hbGxlciBwIHZhbHVlcyBnaXZlbiB0aGUgc2FtZSBlZmZlY3Qgc2l6ZS4gCkxldCdzIG1ha2UgYSBncmFwaCB0byBpbGx1c3RyYXRlIHRoYXQ6CgpgYGB7cn0KdF9kaXN0X2V4YW1wbGUgPC0gZXhwYW5kLmdyaWQoCiAgZGYgPSBjKDIsIDUsIDEwLCA1MCwgODAsIDEwMCksCiAgciA9IGMoMC4yLCAwLjMsIDAuNSwgMC43LCAwLjgsIDAuOSwgMC45OSkKICApICU+JSAKICBtdXRhdGUodCA9IHIqc3FydCgoZGYtMikvKDEtcl4yKSkpICU+JSAKICBtdXRhdGUocCA9IHB0KHEgPSB0LCBkZiA9IGRmLCBsb3dlci50YWlsID0gRikpCiAgCnRfZGlzdF9leGFtcGxlICU+JSAKICBnZ3Bsb3QoYWVzKHggPSByLCB5ID0gLWxvZzEwKHApKSkgKwogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBkZiwgY29sb3IgPSBhcy5mYWN0b3IoZGYpKSwgCiAgICAgICAgICAgIHNpemUgPSAxLjEsIGFscGhhID0gMC44KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMiwgY29sb3IgPSAiZ3JleTIwIiwgc2l6ZSA9IDEsIGxpbmV0eXBlID0gNCkgKwogIGxhYnMoY29sb3IgPSAiZGYiLAogICAgICAgY2FwdGlvbiA9ICJkb3R0ZWQgbGluZTogUCA9IDAuMDEiKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC4yLCAwLjYpLAogICAgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gImJsYWNrIiksCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLCBzaXplID0gMTQpCiAgKQoKZ2dzYXZlKCJ0X2Rpc3RfZXhhbXBsZS5zdmciLCBoZWlnaHQgPSA1LCB3aWR0aCA9IDYsIGJnID0id2hpdGUiKQpnZ3NhdmUoInRfZGlzdF9leGFtcGxlLnBuZyIsIGhlaWdodCA9IDUsIHdpZHRoID0gNiwgYmcgPSJ3aGl0ZSIpCmBgYApBcyB5b3UgY2FuIHNlZSwgbGFyZ2Ugc2l6ZSBleHBlcmltZW50cyAoZGYgPSA4MCBvciAxMDApLCB5b3Ugd291bGQgcmVhY2ggcCA8IDAuMDEgd2l0aCByIHZhbHVlIGJldHdlZW4gMC4yIGFuZCAwLjQuCkhvd2V2ZXIsIGZvciBleHBlcmltZW50cyB3aXRoIGRmIGF0IDUsIHlvdSB3b24ndCBnZXQgdG8gcCA9IDAuMDEgdW5sZXNzIHlvdSBoYXZlIHIgdmFsdWVzIGNsb3NlciB0byAwLjkuIAoKPiBTbyBpdCdzIHJlYWxseSByZWxhdGVkIHRvIGRmLiBJbiBvcmRlciB0byBkaXNyZWdhcmQgdGhlIGVmZmVjdCwgd2UgY2FuIHVzZSBiYWl0IGdlbmVzIHRvIGd1aWQgdGhlIGN1dG9mZi4gRm9yIG5vdywgSSBkb24ndCBoYXZlIG9uZS4KCiMgTW9kdWxlIFNlbGVjdGlvbgoKQXNzaWduIGdlbmVzIHRvIGRpZmZlcmVudCBtb2R1bGVzIGJhc2VkIG9uIHRoZWlyIGNvcnJlbGF0aW9uIGluIGJldHdlZW4sIHRoYXQgaXMsIHRvIGRldGVjdCB0aGUgY28tZXhwcmVzc2VkIGdlbmVzLgoKYGBge3J9Cm5vZGVfdGFibGUgPSBkYXRhLmZyYW1lKAogICAgZ2VuZSA9IGMoZWRnZV90YWJsZV9zZWxlY3QkZnJvbSwgZWRnZV90YWJsZV9zZWxlY3QkdG8pICU+JSB1bmlxdWUoKQogICkKCmhlYWQobm9kZV90YWJsZSkKZGltKG5vZGVfdGFibGUpCmBgYAoKCmBgYHtyfQpteV9uZXR3b3JrID0gZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKAogICAgZWRnZV90YWJsZV9zZWxlY3QsCiAgICB2ZXJ0aWNlcyA9IG5vZGVfdGFibGUsCiAgICBkaXJlY3RlZCA9IEYKICApCmBgYAoKCiMjIEhvdyB0byBkZXRlcm1pbmUgdGhlIHJlc29sdXRpb24gZm9yIGNsdXN0ZXIgbGVpZGVuCmBgYHtyfQpvcHRpbWl6ZV9yZXNvbHV0aW9uID0gZnVuY3Rpb24obmV0d29yaywgcmVzb2x1dGlvbikgewogICAgbW9kdWxlcyA9IG5ldHdvcmsgJT4lIAogICAgICBjbHVzdGVyX2xlaWRlbihyZXNvbHV0aW9uX3BhcmFtZXRlciA9IHJlc29sdXRpb24sIAogICAgICAgICAgICAgICAgICAgICBvYmplY3RpdmVfZnVuY3Rpb24gPSAibW9kdWxhcml0eSIpCiAgCiAgICBwYXJzZWRfbW9kdWxlcyA9IGRhdGEuZnJhbWUoCiAgICAgIGdlbmVfSUQgPSBuYW1lcyhtZW1iZXJzaGlwKG1vZHVsZXMpKSwKICAgICAgbW9kdWxlID0gYXMudmVjdG9yKG1lbWJlcnNoaXAobW9kdWxlcykpCiAgICApCgogICAgbnVtX21vZHVsZV81ID0gcGFyc2VkX21vZHVsZXMgJT4lCiAgICAgIGdyb3VwX2J5KG1vZHVsZSkgJT4lCiAgICAgIGNvdW50KCkgJT4lCiAgICAgIGFycmFuZ2UoLW4pICU+JQogICAgICBmaWx0ZXIobiA+PSA1KSAlPiUKICAgICAgbnJvdygpICU+JQogICAgICBhcy5udW1lcmljKCkKCiAgICBudW1fZ2VuZXNfY29udGFpbmVkID0gcGFyc2VkX21vZHVsZXMgJT4lCiAgICAgIGdyb3VwX2J5KG1vZHVsZSkgJT4lCiAgICAgIGNvdW50KCkgJT4lCiAgICAgIGFycmFuZ2UoLW4pICU+JQogICAgICBmaWx0ZXIobiA+PSA1KSAlPiUKICAgICAgdW5ncm91cCgpICU+JQogICAgICBzdW1tYXJpc2Uoc3VtID0gc3VtKG4pKSAlPiUKICAgICAgYXMubnVtZXJpYygpCgogICAgYyhudW1fbW9kdWxlXzUsIG51bV9nZW5lc19jb250YWluZWQpCn0KYGBgCgoKYGBge3J9Cm9wdGltaXphdGlvbl9yZXN1bHRzX2xpc3QgPSBwdXJycjo6bWFwKAogICAgLnggPSBzZXEoZnJvbSA9IDAuMjUsIHRvID0gNSwgYnkgPSAwLjI1KSwKICAgIC5mID0gb3B0aW1pemVfcmVzb2x1dGlvbiwgCiAgICBuZXR3b3JrID0gbXlfbmV0d29yawogICAgKQoKb3B0aW1pemF0aW9uX3Jlc3VsdHNfZGYgPSBhcy5kYXRhLmZyYW1lKGRvLmNhbGwoY2JpbmQsIG9wdGltaXphdGlvbl9yZXN1bHRzX2xpc3QpKQogIApvcHRpbWl6YXRpb25fcmVzdWx0cyA9IG9wdGltaXphdGlvbl9yZXN1bHRzX2RmICU+JQogICAgdCgpICU+JSAKICAgIGNiaW5kKHJlc29sdXRpb24gPSBzZXEoZnJvbSA9IDAuMjUsIHRvID0gNSwgYnkgPSAwLjI1KSkgJT4lIAogICAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICAgIHJlbmFtZShudW1fbW9kdWxlID0gVjEsIG51bV9jb250YWluZWRfZ2VuZSA9IFYyKQoKT3B0aW1pemVfbnVtX21vZHVsZSA9IG9wdGltaXphdGlvbl9yZXN1bHRzICU+JQogICAgZ2dwbG90KGFlcyh4ID0gcmVzb2x1dGlvbiwgeSA9IG51bV9tb2R1bGUpKSArCiAgICBnZW9tX2xpbmUoc2l6ZSA9IDEuMSwgYWxwaGEgPSAwLjgsIGNvbG9yID0gImRvZGdlcmJsdWUyIikgKwogICAgZ2VvbV9wb2ludChzaXplID0gMywgYWxwaGEgPSAwLjcpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDIsIHNpemUgPSAxLCBsaW5ldHlwZSA9IDQpICsgIyByZXNvbHV0aW9uX3BhcmFtZXRlciB2YXJlaXMsIHN0YXJ0IHRyeWluZyB3aXRoIDEuNQogICAgbGFicyh4ID0gInJlc29sdXRpb24gcGFyYW1ldGVyIiwgeSA9ICJudW0uIG1vZHVsZXNcbncvID49NSBnZW5lcyIpICsKICAgIHRoZW1lX2NsYXNzaWMoKSArIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gImJsYWNrIikpCgpPcHRpbWl6ZV9udW1fZ2VuZSA9IG9wdGltaXphdGlvbl9yZXN1bHRzICU+JQogICAgZ2dwbG90KGFlcyh4ID0gcmVzb2x1dGlvbiwgeSA9IG51bV9jb250YWluZWRfZ2VuZSkpICsKICAgIGdlb21fbGluZShzaXplID0gMS4xLCBhbHBoYSA9IDAuOCwgY29sb3IgPSAidmlvbGV0cmVkMiIpICsKICAgIGdlb21fcG9pbnQoc2l6ZSA9IDMsIGFscGhhID0gMC43KSArCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAyLCBzaXplID0gMSwgbGluZXR5cGUgPSA0KSArCiAgICBsYWJzKHggPSAicmVzb2x1dGlvbiBwYXJhbWV0ZXIiLCB5ID0gIm51bS4gZ2VuZXMgaW5cbm1vZHVsZXMgdy8gPj01IGdlbmVzIikgKwogICAgdGhlbWVfY2xhc3NpYygpICsgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLCBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3IgPSAiYmxhY2siKSkKCk9wdGltaXplX251bV9tb2R1bGUgLyBPcHRpbWl6ZV9udW1fZ2VuZQoKZ2dzYXZlKCJyZXNvbHV0aW9uLnN2ZyIsIGhlaWdodCA9IDUsIHdpZHRoID0gNiwgYmcgPSJ3aGl0ZSIpCmdnc2F2ZSgicmVzb2x1dGlvbi5wbmciLCBoZWlnaHQgPSA1LCB3aWR0aCA9IDYsIGJnID0id2hpdGUiKQpgYGAKCgpgYGB7cn0Kc2V0LnNlZWQoOTg3KQoKbW9kdWxlcyA9IGNsdXN0ZXJfbGVpZGVuKG15X25ldHdvcmssIHJlc29sdXRpb25fcGFyYW1ldGVyID0gMiwgb2JqZWN0aXZlX2Z1bmN0aW9uID0gIm1vZHVsYXJpdHkiKQoKbXlfbmV0d29ya19tb2R1bGVzID0gZGF0YS5mcmFtZSgKICAgIGdlbmUgPSBuYW1lcyhtZW1iZXJzaGlwKG1vZHVsZXMpKSwKICAgIG1vZHVsZSA9IGFzLnZlY3RvcihtZW1iZXJzaGlwKG1vZHVsZXMpKQogICkKCm15X25ldHdvcmtfbW9kdWxlcyAlPiUgCiAgZ3JvdXBfYnkobW9kdWxlKSAlPiUgCiAgY291bnQoKSAlPiUgCiAgYXJyYW5nZSgtbikgJT4lIAogIGZpbHRlcihuID49IDUpIApgYGAKCjEyIG1vZHVsZXMgY29udGFpbiBtb3JlIHRoYW4gNSBnZW5lcywgY292ZXJpbmcgMjU2MCBnZW5lcy4KCmBgYHtyfQpteV9uZXR3b3JrX21vZHVsZXMKYGBgCgojIERHRSBhbmFseXNpcwoKYGBge3J9CmFwcGxlX2V4cF9sb2dfd2lkZSA9IGFwcGxlX2V4cF9sb2dfbG9uZyAlPiUKICBkcGx5cjo6c2VsZWN0KGdlbmUsdGltZXBvaW50LGxvZ19leHApICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSB0aW1lcG9pbnQsIHZhbHVlc19mcm9tID0gbG9nX2V4cCkgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJnZW5lIikKCmhlYWQoYXBwbGVfZXhwX2xvZ193aWRlKQpgYGAKCmBgYHtyfQojIG1hbnVhbGx5IGNhbGN1bGF0ZSBsb2cyZmMKIyB0aGUgcmVhc29uIGZvciBub3QgdXNpbmcgREVTZXEyIGlzIHRoZXkgcmVxdWlyZSByYXcgY291bnQgKGludGVnZXIpIGlucHV0CiMgdGhlIHJlYXNvbiBmb3Igbm90IHVzaW5nIGxpbW1hIGlzIHRoZXkgcmVxdWlyZSBtdWx0aXBsZSByZXBsaWNhdGVzIGZvciBlYWNoIGNvbmRpdGlvbgoKbG9nMl9mYyA8LSBhcHBsZV9leHBfbG9nX3dpZGUgJT4lCiAgbXV0YXRlKAogICAgbG9nMkZDXzM1X3ZzXzYwID0gbG9nMihgMzUgREFBYCAvIGA2MCBEQUFgKSwgICMgY2VsbCBkaXZpc2lvbiBlbmQgdnMgc3RhcmNoIGFjY3VtdWxhdGlvbgogICAgbG9nMkZDXzg3X3ZzXzEzMiA9IGxvZzIoYDg3IERBQWAgLyBgMTMyIERBQWApLCAgIyBzdGFyY2ggYWNjdW11bGF0aW9uIHZzIHJpcGVuaW5nIAogICAgbG9nMkZDXzM1X3ZzXzEzMiA9IGxvZzIoYDM1IERBQWAgLyBgMTMyIERBQWApLCAgIyBjZWxsIGRpdmlzaW9uIHZzIHJpcGVuaW5nCiAgICBsb2cyRkNfNjBfdnNfODcgPSBsb2cyKGA2MCBEQUFgIC8gYDg3IERBQWApICAjIHN0YXJjaCBhY2N1bXVsYXRpb24gdnMgZW5kCiAgKQoKaGVhZChsb2cyX2ZjKQpgYGAKCmBgYHtyfQpyaXBlbmluZ19nZW5lcyA9IGxvZzJfZmMgJT4lCiAgZmlsdGVyKGxvZzJGQ18zNV92c18xMzIgPCAtMSAmIGxvZzJGQ184N192c18xMzIgPCAtMSApICU+JQogIHJvd25hbWVzKCkgIyBJbiBib3RoIGNvbXBhcmlzb24sIHNpZ25pZmljYW50bHkgdXByZWd1bGF0ZWQgaW4gcmlwZW5pbmcgc3RhZ2Vz77yM5Y+v6IO95LiO5p6c5a6e5oiQ54af55u45YWz44CC6L+Z5Lqb5Z+65Zug5Y+v6IO95Y+C5LiO6LCD5o6n5p6c5a6e6LSo5Zyw44CB6aOO5ZGz44CB6Imy5rO955qE5Y+Y5YyW44CCCgpzdGFyY2hfcmVnX2dlbmVzID0gbG9nMl9mYyAlPiUKICBmaWx0ZXIoYWJzKGxvZzJGQ182MF92c184NykgPiAxKSAlPiUgIyA+IDE6IDYwID4gODc7IDwgLTEgNjAgPCA4NwogIHJvd25hbWVzKCkKYGBgCgoKYGBge3J9CnNldC5zZWVkKDk4NykKCmxpYnJhcnkocGhlYXRtYXApCgpoZWF0bWFwX2RhdGFfcmlwZW4gPC0gYXBwbGVfZXhwX2xvZ193aWRlW3JpcGVuaW5nX2dlbmVzLCBdCgojIFNhdmUgYXMgU1ZHCiMgc3ZnbGl0ZSgiaGVhdG1hcF9yaXBlbmluZy5zdmciLCB3aWR0aCA9IDUsIGhlaWdodCA9IDYpCiMgaGVhdG1hcApwaGVhdG1hcCgKICBoZWF0bWFwX2RhdGFfcmlwZW4sIAogIGNsdXN0ZXJfcm93cyA9IFRSVUUsIAogIGNsdXN0ZXJfY29scyA9IEZBTFNFLCAKICBzaG93X3Jvd25hbWVzID0gVFJVRSwgCiAgc2NhbGUgPSAicm93IgopCiMgZGV2Lm9mZigpCmBgYAoKYGBge3J9CnNldC5zZWVkKDk4NykKCmxpYnJhcnkocGhlYXRtYXApCgpoZWF0bWFwX2RhdGFfc3RhcmNoIDwtIGFwcGxlX2V4cF9sb2dfd2lkZVtzdGFyY2hfcmVnX2dlbmVzLCBdCiMgc3ZnbGl0ZSgiaGVhdG1hcF9zdGFyY2guc3ZnIiwgd2lkdGggPSA1LCBoZWlnaHQgPSA2KQojIGhlYXRtYXAKcGhlYXRtYXAoCiAgaGVhdG1hcF9kYXRhX3N0YXJjaCwgCiAgY2x1c3Rlcl9yb3dzID0gVFJVRSwgCiAgY2x1c3Rlcl9jb2xzID0gRkFMU0UsIAogIHNob3dfcm93bmFtZXMgPSBGQUxTRSwgCiAgc2NhbGUgPSAicm93IgopCiMgZGV2Lm9mZigpCgojIG1hbnkgZ2VuZXMKYGBgCgpgYGB7cn0KcGhlYXRtYXAoCiAgYXBwbGVfZXhwX2xvZ193aWRlLAogIGNsdXN0ZXJfcm93cyA9IEZBTFNFLCAKICBjbHVzdGVyX2NvbHMgPSBGQUxTRSwgCiAgc2hvd19yb3duYW1lcyA9IEZBTFNFLCAKICBzY2FsZSA9ICJyb3ciCikKYGBgCgoKCgpgYGB7cixldmFsPUZBTFNFfQpzYW1wbGVfaW5mbyA9IGRhdGEuZnJhbWUoCiAgc2FtcGxlID0gY29sbmFtZXMoYXBwbGVfZXhwX2xvZ193aWRlKSwKICBncm91cCA9IGMoIlQwIiwgIlQxNCIsICJUMjUiLCAiVDM1IiwgIlQ2MCIsICJUODciLCAiVDEzMiIsICJUMTQ2IikKKQoKc2FtcGxlX2luZm8kZ3JvdXAgPC0gZmFjdG9yKHNhbXBsZV9pbmZvJGdyb3VwLCBsZXZlbHMgPSB1bmlxdWUoc2FtcGxlX2luZm8kZ3JvdXApKQpgYGAKCgojIEVucmljaG1lbnQgY2FjdWxhdGlvbiBmb3IgY2VydGFpbiBnZW5lcyAKYGBge3J9CiMgQ2FsY3VsYXRlIGVucmljaG1lbnQgZm9yIG1vZHVsZXMgZnVuY3Rpb24KIyBNYWtlIHN1cmUgeW91IGdvdCB0aGUgbW9kdWxlX2luZm9fZGYuCiMgVGhlIGZvcm1hdCBvZiBtb2R1bGUgaW5mbyBkZiBzaG91bGQgYmUgYSAyIGNvbHVtbnMgdGliYmxlIHdpdGggZmlyc3QgY29sdW1uIG5hbWVkIGdlbmUsIHNlY29uZCBjb2x1bW4gbmFtZWQgbW9kdWxlLgojIFRoZSBpbnRlcmVzdGluZ19nZW5lcyBwYXJ0IHNob3VsZCBiZSBhIHNldCBvZiBnZW5lcy4KY2FsY3VsYXRlX21vZHVsZV9lbnJpY2htZW50ID0gZnVuY3Rpb24obW9kdWxlX2luZm9fZGYsIGludGVyZXN0aW5nX2dlbmVzKSB7CiAgbGlicmFyeShicm9vbSkKICBsaWJyYXJ5KHRpZHl2ZXJzZSkKCiAgZGYxID0gbW9kdWxlX2luZm9fZGYgJT4lCiAgICBncm91cF9ieShtb2R1bGUpICU+JQogICAgc3VtbWFyaXNlKAogICAgICByYXdfY291bnQgPSBuKCksCiAgICApICU+JQogICAgdW5ncm91cCgpCiAgCiAgZGYyID0gZGF0YS5mcmFtZShnZW5lID0gaW50ZXJlc3RpbmdfZ2VuZXMpICU+JQogICAgbGVmdF9qb2luKG1vZHVsZV9pbmZvX2RmLCBieSA9ICJnZW5lIikgJT4lCiAgICBncm91cF9ieShtb2R1bGUpICU+JQogICAgc3VtbWFyaXNlKAogICAgICByYXdfY291bnQgPSBuKCksCiAgICApICU+JQogICAgdW5ncm91cCgpCiAgCiAgYWxsX21vZHVsZXMgPSB1bmlxdWUoZGYxJG1vZHVsZSkKCiAgcmVzdWx0X2RmID0gdGliYmxlKCkgCiAgCiAgZm9yIChpIGluIDE6bGVuZ3RoKGFsbF9tb2R1bGVzKSkgewogICAgbW9kdWxlID0gYWxsX21vZHVsZXNbaV0KCiAgICByYXdfY291bnRfZGYxID0gZGYxW2RmMSRtb2R1bGUgPT0gbW9kdWxlLCAicmF3X2NvdW50Il0KICAgIHJhd19jb3VudF9kZjIgPSBuYS5vbWl0KGRmMltkZjIkbW9kdWxlID09IG1vZHVsZSwgInJhd19jb3VudCJdKQoKICAgIGlmIChucm93KHJhd19jb3VudF9kZjIpID09IDApIHsKICAgICAgICByYXdfY291bnRfZGYyID0gMAogICAgfQogICAgdG90YWxfY291bnRfZGYxID0gc3VtKGRmMSRyYXdfY291bnQpCiAgICB0b3RhbF9jb3VudF9kZjIgPSBzdW0oZGYyJHJhd19jb3VudCkKICAgIAogICAgY29udGluZ2VuY3lfdGFibGUgPSBtYXRyaXgoYyhzdW0ocmF3X2NvdW50X2RmMiksIAogICAgICAgICBzdW0ocmF3X2NvdW50X2RmMSksIAogICAgICAgICB0b3RhbF9jb3VudF9kZjIgLSBzdW0ocmF3X2NvdW50X2RmMiksCiAgICAgICAgIHRvdGFsX2NvdW50X2RmMSAtIHN1bShyYXdfY291bnRfZGYxKSksCiAgICAgICBucm93ID0gMikKICAgIAogICAgZmlzaGVyX3Rlc3RfcmVzdWx0ID0gdGlkeShmaXNoZXIudGVzdChjb250aW5nZW5jeV90YWJsZSwgYWx0ZXJuYXRpdmUgPSAiZyIpKQogICAgCiAgICBwZXJjZW50YWdlID0gcGFzdGUocm91bmQoc3VtKHJhd19jb3VudF9kZjIpIC8gdG90YWxfY291bnRfZGYyICogMTAwLCAyKSwgIiUgLyAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoc3VtKHJhd19jb3VudF9kZjEpIC8gdG90YWxfY291bnRfZGYxICogMTAwLCAyKSwgIiUiLCBzZXAgPSAiIikKICAgIAogICAgdGVtcF9kZiA9IHRpYmJsZShtb2R1bGUgPSBtb2R1bGUsCiAgICAgICAgICAgICAgICAgICAgIHJhd19jb3VudCA9IHBhc3RlKHJhd19jb3VudF9kZjIsIHJhd19jb3VudF9kZjEsIHNlcCA9ICIvIiksCiAgICAgICAgICAgICAgICAgICAgIHBlcmNlbnRhZ2UgPSBwZXJjZW50YWdlKQogICAgCiAgICB0ZW1wX2RmID0gYmluZF9jb2xzKHRlbXBfZGYsIGZpc2hlcl90ZXN0X3Jlc3VsdCkgCiAgICByZXN1bHRfZGYgPSBiaW5kX3Jvd3MocmVzdWx0X2RmLCB0ZW1wX2RmKSAKICB9CiAgCiAgcmV0dXJuKHJlc3VsdF9kZikKfQpgYGAKCgoKIyBNb2R1bGUgdHJlbmRzCgpgYGB7cn0KbW9kdWxlX21lYW5zIDwtIGFwcGxlX2V4cF9sb2dfaGlnaHZhciAlPiUKICBkcGx5cjo6c2VsZWN0KGdlbmUsIHRpbWVwb2ludCwgbG9nX2V4cCkgJT4lCiAgbGVmdF9qb2luKG15X25ldHdvcmtfbW9kdWxlcywgYnkgPSBqb2luX2J5KGdlbmUpKSAlPiUKICBmaWx0ZXIobW9kdWxlICVpbiUgYygzLCAyLCA0LCA3LCA5KSkgJT4lCiAgbXV0YXRlKHRpbWVwb2ludCA9IGZhY3Rvcih0aW1lcG9pbnQsIGxldmVscyA9IGMoIjAgREFBIiwgIjE0IERBQSIsICIyNSBEQUEiLCAiMzUgREFBIiwgIjYwIERBQSIsICI4NyBEQUEiLCAiMTMyIERBQSIsICIxNDYgREFBIikpKSAlPiUKICBncm91cF9ieShtb2R1bGUsdGltZXBvaW50KSAlPiUKICBzdW1tYXJpc2UobWVhbl9leHAgPSBtZWFuKGxvZ19leHAsIG5hLnJtID0gVFJVRSksIC5ncm91cHMgPSAiZHJvcCIpCgphcHBsZV9leHBfbG9nX2hpZ2h2YXIgJT4lCiAgZHBseXI6OnNlbGVjdChnZW5lLCB0aW1lcG9pbnQsIGxvZ19leHApICU+JQogIGxlZnRfam9pbihteV9uZXR3b3JrX21vZHVsZXMsIGJ5ID0gam9pbl9ieShnZW5lKSkgJT4lCiAgZmlsdGVyKG1vZHVsZSAlaW4lIGMoMywgMiwgNCwgNywgOSkpICU+JQogIG11dGF0ZSh0aW1lcG9pbnQgPSBmYWN0b3IodGltZXBvaW50LCBsZXZlbHMgPSBjKCIwIERBQSIsICIxNCBEQUEiLCAiMjUgREFBIiwgIjM1IERBQSIsICI2MCBEQUEiLCAiODcgREFBIiwgIjEzMiBEQUEiLCAiMTQ2IERBQSIpKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdGltZXBvaW50LCB5ID0gbG9nX2V4cCwgZ3JvdXAgPSBnZW5lKSkgKwogICMg5aSn6YOo5YiG5Z+65Zug54Gw6Imy57q/CiAgZ2VvbV9saW5lKGNvbG9yID0gImdyZXkiLCBhbHBoYSA9IDAuNSkgKwogICMg5re75Yqg5qih5Z2X5Z2H5YC857KX57q/CiAgZ2VvbV9saW5lKGRhdGEgPSBtb2R1bGVfbWVhbnMsIGFlcyh4ID0gdGltZXBvaW50LCB5ID0gbWVhbl9leHAsIGNvbG9yID0gYXMuZmFjdG9yKG1vZHVsZSksIGdyb3VwID0gbW9kdWxlKSwgc2l6ZSA9IDEuNSkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicygKICAgIHRpdGxlID0gIkV4cHJlc3Npb24gVHJlbmRzIGZvciBTZWxlY3RlZCBNb2R1bGVzIiwKICAgIHggPSAiVGltZXBvaW50IChEQUEpIiwKICAgIHkgPSAiRXhwcmVzc2lvbiBWYWx1ZSIsCiAgICBjb2xvciA9ICJNb2R1bGUiCiAgKSArCiAgZmFjZXRfd3JhcCh+bW9kdWxlLCBzY2FsZXMgPSAiZnJlZV95IikgKyAgIyDliIbpnaLlsZXnpLrmr4/kuKrmqKHlnZcKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQoKZ2dzYXZlKCJleHByZXNzaW9uX3RyZW5kc190b3BfbW9kdWxlcy5zdmciLCBoZWlnaHQgPSA2LCB3aWR0aCA9MTAsIGJnID0id2hpdGUiKQpnZ3NhdmUoImV4cHJlc3Npb25fdHJlbmRzX3RvcF9tb2R1bGVzLnBuZyIsIGhlaWdodCA9IDYsIHdpZHRoID0xMCwgYmcgPSJ3aGl0ZSIpCmBgYAoKYGBge3J9CmNhbGN1bGF0ZV9tb2R1bGVfZW5yaWNobWVudChteV9uZXR3b3JrX21vZHVsZXMscmlwZW5pbmdfZ2VuZXMpCmBgYAoKYGBge3J9CmFwcGxlX2V4cF9sb2dfaGlnaHZhciAlPiUKICBkcGx5cjo6c2VsZWN0KGdlbmUsIHRpbWVwb2ludCwgbG9nX2V4cCkgJT4lCiAgbGVmdF9qb2luKG15X25ldHdvcmtfbW9kdWxlcywgYnkgPSBqb2luX2J5KGdlbmUpKSAlPiUKICBmaWx0ZXIoZ2VuZSAlaW4lIHJpcGVuaW5nX2dlbmVzKSAlPiUKICBtdXRhdGUodGltZXBvaW50ID0gZmFjdG9yKHRpbWVwb2ludCwgbGV2ZWxzID0gYygiMCBEQUEiLCAiMTQgREFBIiwgIjI1IERBQSIsICIzNSBEQUEiLCAiNjAgREFBIiwgIjg3IERBQSIsICIxMzIgREFBIiwgIjE0NiBEQUEiKSkpICU+JQogIGdncGxvdChhZXMoeCA9IHRpbWVwb2ludCwgeSA9IGxvZ19leHAsIGdyb3VwID0gZ2VuZSwgY29sb3IgPSBnZW5lKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9saW5lKHNpemUgPSAwLjUsIGFscGhhID0gMC44KSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRXhwcmVzc2lvbiBUcmVuZHMgb2YgUmlwZW5pbmcgUHJvYmVzIiwKICAgIHggPSAiVGltZXBvaW50IChEQUEpIiwKICAgIHkgPSAiTG9nIEV4cHJlc3Npb24gVmFsdWUiLAogICAgY29sb3IgPSAiR2VuZSIKICApICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCmdnc2F2ZSgiZXhwcmVzc2lvbl90cmVuZHNfcmlwZW5pbmdfbG9nLnN2ZyIsIGhlaWdodCA9IDUsIHdpZHRoID0gNywgYmcgPSJ3aGl0ZSIpCmdnc2F2ZSgiZXhwcmVzc2lvbl90cmVuZHNfcmlwZW5pbmdfbG9nLnBuZyIsIGhlaWdodCA9IDUsIHdpZHRoID0gNywgYmcgPSJ3aGl0ZSIpCmBgYAoKCmBgYHtyfQphcHBsZV9leHAgJT4lCiAgZ3JvdXBfYnkoYEdlbmJhbmsgbnVtYmVyYCkgJT4lCiAgbXV0YXRlKGdlbmUgPSBjYXNlX3doZW4oICMgbXV0YXRlIGEgbmV3IGNvbCAKICAgIG4oKSA+IDEgfiBwYXN0ZTAoYEdlbmJhbmsgbnVtYmVyYCwgIl8iLCByb3dfbnVtYmVyKCkpLCAjICJkdXBsaWNhdGVzIiB3aWxsIGhhdmUgbW9yZSB0aGFuIDEgcm93LiBBZGQgYSBzeW1ib2wuCiAgICBUUlVFIH4gYEdlbmJhbmsgbnVtYmVyYCAjIGtlZXAgdGhlIHNhbWUgZm9yIHRob3NlIG5vdF9kdXBsaWNhdGVkCiAgKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIHJlbmFtZV93aXRoKH4gZ3N1YigiIE1lYW4iLCAiIiwgLiksIGNvbnRhaW5zKCJEQUEiKSkgJT4lICMgUmVtb3ZlIHRoZSAiTWVhbiIgaW4gY29sbmFtZXMKICBwaXZvdF9sb25nZXIoCiAgICBjb2xzID0gc3RhcnRzX3dpdGgoIjAgREFBIik6c3RhcnRzX3dpdGgoIjE0NiBEQUEiKSwgCiAgICBuYW1lc190byA9ICJ0aW1lcG9pbnQiLCAKICAgIHZhbHVlc190byA9ICJleHByZXNzaW9uIgogICkgJT4lCiAgZHBseXI6OnNlbGVjdChnZW5lLCB0aW1lcG9pbnQsIGV4cHJlc3Npb24pICU+JQogIGxlZnRfam9pbihteV9uZXR3b3JrX21vZHVsZXMsIGJ5ID0gam9pbl9ieShnZW5lKSkgJT4lCiAgZmlsdGVyKGdlbmUgJWluJSByaXBlbmluZ19nZW5lcykgJT4lCiAgbXV0YXRlKHRpbWVwb2ludCA9IGZhY3Rvcih0aW1lcG9pbnQsIGxldmVscyA9IGMoIjAgREFBIiwgIjE0IERBQSIsICIyNSBEQUEiLCAiMzUgREFBIiwgIjYwIERBQSIsICI4NyBEQUEiLCAiMTMyIERBQSIsICIxNDYgREFBIikpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB0aW1lcG9pbnQsIHkgPSBleHByZXNzaW9uLCBncm91cCA9IGdlbmUsIGNvbG9yID0gZ2VuZSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZShzaXplID0gMC41LCBhbHBoYSA9IDAuOCkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicygKICAgIHRpdGxlID0gIkV4cHJlc3Npb24gVHJlbmRzIG9mIFJpcGVuaW5nIFByb2JlcyIsCiAgICB4ID0gIlRpbWVwb2ludCAoREFBKSIsCiAgICB5ID0gIkV4cHJlc3Npb24gVmFsdWUiLAogICAgY29sb3IgPSAiR2VuZSIKICApICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCmdnc2F2ZSgiZXhwcmVzc2lvbl90cmVuZHNfcmlwZW5pbmcuc3ZnIiwgaGVpZ2h0ID0gNSwgd2lkdGggPSA3LCBiZyA9IndoaXRlIikKZ2dzYXZlKCJleHByZXNzaW9uX3RyZW5kc19yaXBlbmluZy5wbmciLCBoZWlnaHQgPSA1LCB3aWR0aCA9IDcsIGJnID0id2hpdGUiKQpgYGAKCmBgYHtyfQpjYWxjdWxhdGVfbW9kdWxlX2VucmljaG1lbnQobXlfbmV0d29ya19tb2R1bGVzLGJhaXRfZ2VuZXMpCmBgYAoKU2VlbXMgbGlrZSB0aGUgQ0RLQjIgYW5kIENLUzEgaG9tb2xvZ3VlcyBhcmUgYXNzaWduZWQgKGNsdXN0ZXJlZCkgdG9nZXRoZXIgYmFzZWQgb24gdGhlIHRyZW5kIHdoaWxlIENES0IxIGlzIHNlcGFyYXRlZC4KCmBgYHtyfQpjYWxjdWxhdGVfbW9kdWxlX2VucmljaG1lbnQobXlfbmV0d29ya19tb2R1bGVzLHN0YXJjaF9yZWdfZ2VuZXMpCmBgYAoK